﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using D3D = Microsoft.DirectX.Direct3D;
using Microsoft.DirectX.DirectSound;
using DSound = Microsoft.DirectX.DirectSound;
using Microsoft.DirectX.AudioVideoPlayback;
using System.Drawing;
using EdytorWF.Objects;

namespace EdytorWF.Engine
{
    enum EViewType
    {
        Editor = 0,
        Normals = 1,
        Dof = 2,
        Game = 3,
    };

    class Renderer : IDisposable
    {

        public Control Control { get; set; }
        public D3D.Device MyDevice { get; set; }

        public DSound.Device MySoundDevice { get; set; }
        private Audio Music;

        public List<ViewInfo> ListInfo { get; set; }
        public List<IObject> ObjectList { get; set; }
        public List<LightObject> LigthList { get; set; }

        public Camera ViewCamera { get; set; }
        public Environment MyEnvironment { get; set; }

        private FillMode DeviceMode;

        private D3D.Effect DefaultEffect;
        private D3D.Effect HelpEffects;
        private D3D.Effect FogEffect;
        private Matrix mProjection, mView;

        public List<DSound.Listener3D> SoundList;

        private Texture FogPostProcess;
        private Texture ScreenTexture;

        private RenderToSurface RenderToFogSurface;
        private RenderToSurface RenderToScreenSurface;

        ModelObject CollisionHelper;
        bool isHelperRender { get; set; }

        public EViewType TypeView;

        public void Initialize(Control control, ref Camera view_camera)
        {
            Control = control;
            ListInfo = new List<ViewInfo>();
            ViewCamera = view_camera;
            DeviceMode = FillMode.Solid;
            ObjectList = new List<IObject>();
            LigthList = new List<LightObject>();
            CollisionHelper = null;
            isHelperRender = true;

            InitializeDevice();
            InitialSound();
            InitialCamera();
            InitialLight();

            SoundList = new List<Listener3D>();
            MyEnvironment = new Environment(MyDevice);

            CollisionHelper = new ModelObject(MyDevice, new Vector3(0, 0, 0), true);

            TypeView = EViewType.Editor;
        }

        //////////////////////////////////////////////////////////////////////////
        private void InitializeDevice()
        {
            if (Control == null)
                return;

            PresentParameters parameters = new PresentParameters
            {
                Windowed = true,
                SwapEffect = SwapEffect.Discard,
                AutoDepthStencilFormat = DepthFormat.D16,
                EnableAutoDepthStencil = true
            };

            MyDevice = new D3D.Device(0, DeviceType.Hardware, Control, CreateFlags.HardwareVertexProcessing, parameters);
            DefaultEffect = D3D.Effect.FromFile(MyDevice, @"..\..\effects\default.fx", null, ShaderFlags.None, null);
            HelpEffects = D3D.Effect.FromFile(MyDevice, @"..\..\effects\helpRenders.fx", null, ShaderFlags.None, null);
            FogEffect = D3D.Effect.FromFile(MyDevice, @"..\..\effects\effect_dof.fx", null, ShaderFlags.None, null);

            RenderToFogSurface = new RenderToSurface(MyDevice, this.Control.Width, this.Control.Height, Format.X8R8G8B8, true, DepthFormat.D16);
            RenderToScreenSurface = new RenderToSurface(MyDevice, this.Control.Width, this.Control.Height, Format.X8R8G8B8, true, DepthFormat.D16);

            PrepareScreenStrip();
        }

        //////////////////////////////////////////////////////////////////////////
        private void InitialCamera()
        {
            if (ViewCamera == null)
                return;

            ViewCamera.AspectRatio = Control.Width / (float)Control.Height;
            MyDevice.Transform.Projection = mProjection = Matrix.PerspectiveFovLH(ViewCamera.Fov, ViewCamera.AspectRatio,
                ViewCamera.ZNear, ViewCamera.ZFar);

            MyDevice.Transform.View = mView = Matrix.LookAtLH(
                ViewCamera.Position, // camera position
                ViewCamera.Forward + ViewCamera.Position, // look at position
                ViewCamera.Up);

            MyDevice.Transform.World = Matrix.Identity;
        }

        //////////////////////////////////////////////////////////////////////////
        private void InitialLight()
        {
            if (MyDevice == null)
                return;

            MyDevice.Lights[0].Type = LightType.Directional;
            MyDevice.Lights[0].Diffuse = Color.White;
            MyDevice.Lights[0].Direction = new Vector3(1, 1, 0);
            MyDevice.Lights[0].Enabled = true;
            MyDevice.RenderState.Lighting = true;
        }

        //////////////////////////////////////////////////////////////////////////
        private void InitialSound()
        {
            if (Control == null)
                return;

            MySoundDevice = new DSound.Device();
            MySoundDevice.SetCooperativeLevel(Control, CooperativeLevel.Normal);

            Music = new Audio("..\\..\\Resources\\Sounds\\again.mp3", false);
        }

        //////////////////////////////////////////////////////////////////////////
        public void UpdateListenerPosition()
        {
            Listener3DOrientation LO = new Listener3DOrientation();
            Vector3 TempVec = new Vector3(0, 1, 0);
            Vector4 V = Vector3.Transform(TempVec, Matrix.RotationQuaternion(ViewCamera.RotationQuaternion));
            LO.Front = new Vector3(V.X, V.Y, V.Z);
            TempVec = new Vector3(0, 0, 1);
            V = Vector3.Transform(TempVec, Matrix.RotationQuaternion(ViewCamera.RotationQuaternion));
            LO.Top = new Vector3(V.X, V.Y, V.Z);

            foreach (DSound.Listener3D l in SoundList)
            {
                l.Position = ViewCamera.Position;
                l.Orientation = LO;
            }
        }

        //////////////////////////////////////////////////////////////////////////
        public void Dispose()
        {
            if (MyDevice == null)
                return;

            MyDevice.Dispose();
            GC.SuppressFinalize(this);
        }

        //////////////////////////////////////////////////////////////////////////
        public bool CreateModelObject()
        {
            ModelObject model_object = new ModelObject(MyDevice, ViewCamera.Position);
            if (model_object != null)
            {
                ObjectList.Add(model_object);
                return true;
            }

            return false;
        }

        //////////////////////////////////////////////////////////////////////////
        public bool CreateModelObject(Vector3 init_point)
        {
            ModelObject model_object = new ModelObject(MyDevice, init_point);
            if (model_object != null)
            {
                ObjectList.Add(model_object);
                return true;
            }

            return false;
        }

        //////////////////////////////////////////////////////////////////////////
        public bool CreateParticleObject(Vector3 init_point)
        {
            ObjectList.Add(new ParticleObject(MyDevice, init_point));
            return true;
        }

        //////////////////////////////////////////////////////////////////////////
        public bool CreateSoundObject(Vector3 init_point)
        {
            SoundObject so = new SoundObject(MyDevice, MySoundDevice);
            if (so != null)
            {
                so.Move(init_point);
                so.Helper = 1.0f;
                SoundList.Add(so.GetListener());
                ObjectList.Add(so);
                return true;
            }
            return false;
        }

        //////////////////////////////////////////////////////////////////////////
        public bool CreateLightObject(Vector3 init_point)
        {
            bool ret = false;
            LightObject lo = new LightObject(MyDevice);
            if (lo != null)
            {
                lo.Move(init_point);
                lo.Helper = 1.0f;
                LigthList.Add(lo);
                ObjectList.Add(lo);
                ret = true;
            }
            UpdateLights();
            return ret;
        }

        //////////////////////////////////////////////////////////////////////////
        public void Render()
        {
            if (MyDevice == null)
                return;

            MyDevice.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.White, 1.0f, 0);

            MyDevice.BeginScene();
            MyDevice.RenderState.FillMode = DeviceMode;

            foreach (ViewInfo i in ListInfo)
            {
                i.Render();
            }
            MyDevice.EndScene();

            MyEnvironment.InitRenderOptions(mView, mProjection, new Vector3(ViewCamera.Position.X, ViewCamera.Position.Y, ViewCamera.Position.Z));


            if (TypeView == EViewType.Normals)
            {
                MyDevice.BeginScene();

                MyEnvironment.RenderTerrain("normals");
                DefaultEffect.Technique = "normals";
                DefaultEffect.CommitChanges();

                RenderObjects(ref DefaultEffect);

                MyDevice.EndScene();
            }
            else if (TypeView == EViewType.Dof)
            {
                if (FogPostProcess != null)
                    FogPostProcess.Dispose();
                if (ScreenTexture != null)
                    ScreenTexture.Dispose();

                FogPostProcess = new Texture(MyDevice, this.Control.Width, this.Control.Height, 1, Usage.RenderTarget, Format.X8R8G8B8, Pool.Default);
                ScreenTexture = new Texture(MyDevice, this.Control.Width, this.Control.Height, 1, Usage.RenderTarget, Format.X8R8G8B8, Pool.Default);

                RenderToFogSurface.Dispose();
                RenderToScreenSurface.Dispose();
                RenderToFogSurface = new RenderToSurface(MyDevice, this.Control.Width, this.Control.Height, Format.X8R8G8B8, true, DepthFormat.D16);
                RenderToScreenSurface = new RenderToSurface(MyDevice, this.Control.Width, this.Control.Height, Format.X8R8G8B8, true, DepthFormat.D16);

                FogEffect.Technique = "dof";
                FogEffect.SetValue("xDX", 6.0f / (float)this.Control.Width);
                FogEffect.SetValue("xDY", 6.0f / (float)this.Control.Height);

                FogEffect.SetValue("xDOFDistance", 50f);
                FogEffect.SetValue("xDOFRange", 15.0f);
                FogEffect.SetValue("xDOFFactor", 0.01f);

                RenderFog();

                FogEffect.SetValue("xDOFMap", FogPostProcess);

                RenderScene();

                FogEffect.SetValue("xTexture", ScreenTexture);

                MyDevice.BeginScene(); // początek sceny 
                MyDevice.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Yellow, 1.0f, 0);

                RenderTextureToScreen();

                MyDevice.EndScene();
            }
            else if (TypeView == EViewType.Game)
            {
                MyDevice.BeginScene();

                DefaultEffect.Technique = "simplest";

                DefaultEffect.SetValue("xWorld", Matrix.Identity);
                DefaultEffect.SetValue("xView", mView);
                DefaultEffect.SetValue("xProjection", mProjection);
                DefaultEffect.CommitChanges();

                RenderObjects(ref DefaultEffect, false);

                MyEnvironment.RenderTerrain(null);

                MyDevice.EndScene();
            }
            else
            {
                MyDevice.BeginScene();

                DefaultEffect.Technique = "simplest";

                DefaultEffect.SetValue("xWorld", Matrix.Identity);
                DefaultEffect.SetValue("xView", mView);
                DefaultEffect.SetValue("xProjection", mProjection);
                DefaultEffect.CommitChanges();

                RenderObjects(ref DefaultEffect);

                MyEnvironment.RenderTerrain(null);

                MyDevice.EndScene();
            }

            MyDevice.BeginScene();
            RenderHelpers();
            MyDevice.EndScene();

            MyDevice.Present();
        }

        //////////////////////////////////////////////////////////////////////////
        private void RenderHelpers()
        {
            if (isHelperRender)
            {
                if (CollisionHelper != null)
                {
                    HelpEffects.Technique = "simplest";
                    HelpEffects.SetValue("xWorld", Matrix.Identity);
                    HelpEffects.SetValue("xView", mView);
                    HelpEffects.SetValue("xProjection", mProjection);
                    HelpEffects.CommitChanges();

                    int passes = HelpEffects.Begin(0);
                    for (int i = 0; i < passes; i++)
                    {
                        HelpEffects.BeginPass(i);
                        CollisionHelper.Render();
                        HelpEffects.EndPass();
                    }
                    HelpEffects.End();
                }
            }
        }
        //////////////////////////////////////////////////////////////////////////
        public void SetCollisionHelper(Vector3 collision_point)
        {
            Vector3 pos1 = new Vector3();
            pos1 = ViewCamera.Position - 0.1f * ViewCamera.Right;
            Vector3 pos2 = new Vector3();
            pos2 = ViewCamera.Position + 0.1f * ViewCamera.Right;

            CustomVertex.PositionNormalTextured[] vertices = new CustomVertex.PositionNormalTextured[3];
            vertices[0].Position = new Vector3(pos1.X, pos1.Y - 1, pos1.Z);
            vertices[0].Normal = new Vector3(1, 1, 1);
            vertices[1].Position = new Vector3(pos2.X, pos2.Y - 1, pos2.Z);
            vertices[1].Normal = new Vector3(1, 1, 1);
            vertices[2].Position = new Vector3(collision_point.X, collision_point.Y, collision_point.Z);
            vertices[2].Normal = new Vector3(1, 1, 1);

            List<int> indices = new List<int>() { 2, 1, 0 };

            CollisionHelper.SetMeshAttribs(vertices.ToList(), indices);
        }

        //////////////////////////////////////////////////////////////////////////
        public void SetCamera(ref Camera camera)
        {
            ViewCamera = camera;
            UpdateCamera();
        }

        //////////////////////////////////////////////////////////////////////////
        public void UpdateCamera()
        {
            if (ViewCamera == null)
                return;

            ViewCamera.AspectRatio = Control.Width / (float)Control.Height;
            MyDevice.Transform.Projection = mProjection = Matrix.PerspectiveFovLH(ViewCamera.Fov, ViewCamera.AspectRatio,
                ViewCamera.ZNear, ViewCamera.ZFar);

            MyDevice.Transform.View = mView = Matrix.LookAtLH(
                ViewCamera.Position, // camera position
                ViewCamera.Forward + ViewCamera.Position, // look at position
                ViewCamera.Up);

            MyDevice.Transform.World = Matrix.Identity;

            UpdateListenerPosition();
        }

        //////////////////////////////////////////////////////////////////////////

        public void MoveSun(float da, float db, float dr)
        {
            MyEnvironment.MoveSun(da, db, dr);
            Render();
        }

        //////////////////////////////////////////////////////////////////////////
        public void UpdateLights()
        {
            int count = 0;
            uint light_count = 0;
            foreach (IObject o in ObjectList)
            {
                if (o.GetTypeName() != "_MODEL_OBJECT")
                    continue;

                ModelObject m_object = (ModelObject)o;
                light_count = 0;

                foreach (LightObject l in LigthList)
                {
                    Vector3 start_pos = new Vector3(l.Position.X - l.Range, l.Position.Y - l.Range, l.Position.Z - l.Range);
                    Vector3 end_pos = new Vector3(l.Position.X + l.Range, l.Position.Y + l.Range, l.Position.Z + l.Range);

                    BoundingBox box = new BoundingBox(start_pos, end_pos);
                    if (IEngine.EngineInstance.IsPointInBox(m_object.Position, box))
                    {
                        m_object.SetLight(l, (int)light_count);
                        ++light_count;
                    }
                    ++count;
                }
                if (light_count < 6)
                {
                    for (uint i = light_count; i < 6; ++i)
                    {
                        m_object.SetLight(null, (int)i);
                    }
                }
            }
            MyDevice.RenderState.Lighting = true;
            Render();
        }

        //////////////////////////////////////////////////////////////////////////
        public void AddViewInfo(ref ViewInfo info)
        {
            ListInfo.Add(info);
        }

        //////////////////////////////////////////////////////////////////////////
        public void SetWireFrame()
        {
            DeviceMode = FillMode.WireFrame;
        }

        //////////////////////////////////////////////////////////////////////////
        public void SetSolid()
        {
            DeviceMode = FillMode.Solid;
        }

        //////////////////////////////////////////////////////////////////////////
        public void GetControlSize(out float width, out float height)
        {
            if (Control != null)
            {
                width = Control.Width;
                height = Control.Height;
                return;
            }
            width = 0;
            height = 0;
        }

        //////////////////////////////////////////////////////////////////////////
        public void SetTypeView(EViewType type)
        {
            TypeView = type;
            Render();
        }

        //////////////////////////////////////////////////////////////////////////
        VertexBuffer mvb;
        VertexDeclaration mvd;

        struct myownvertexformat
        {
            public Vector3 Pos;
            public Vector2 TexCoord;

            public myownvertexformat(Vector3 _Pos, float texx, float texy)
            {
                Pos = _Pos;
                TexCoord.X = texx;
                TexCoord.Y = texy;
            }
        }
        //////////////////////////////////////////////////////////////////////////
        public void PrepareScreenStrip()
        {
            mvb = new VertexBuffer(typeof(myownvertexformat), 4, MyDevice, Usage.WriteOnly, VertexFormats.Position | VertexFormats.Texture0, Pool.Managed);

            myownvertexformat[] vertices = new myownvertexformat[4];

            vertices[0] = new myownvertexformat(new Vector3(-1, 1, 0), 0.0f, 0.0f);
            vertices[1] = new myownvertexformat(new Vector3(1, 1, 0), 1.0f, 0.0f);
            vertices[2] = new myownvertexformat(new Vector3(-1, -1, 0), 0.0f, 1.0f);
            vertices[3] = new myownvertexformat(new Vector3(1, -1, 0), 1.0f, 1.0f);

            mvb.SetData(vertices, 0, LockFlags.None);

            VertexElement[] velements = new VertexElement[]
             {
                 new VertexElement(0, 0, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Position, 0),
                 new VertexElement(0, 12, DeclarationType.Float2, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 0),
                 VertexElement.VertexDeclarationEnd
             };
            mvd = new VertexDeclaration(MyDevice, velements);

        }
        //////////////////////////////////////////////////////////////////////////
        private void RenderTextureToScreen()
        {
            FogEffect.Technique = "T2SFog";
            MyDevice.SetStreamSource(0, mvb, 0);
            MyDevice.VertexDeclaration = mvd;
            FogEffect.CommitChanges();

            int numpasses = FogEffect.Begin(0);
            for (int i = 0; i < numpasses; i++)
            {
                FogEffect.BeginPass(i);

                MyDevice.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2);

                FogEffect.EndPass();
            }
            FogEffect.End();
        }
        //////////////////////////////////////////////////////////////////////////
        private void RenderFog()
        {
            Surface RenderSurface = FogPostProcess.GetSurfaceLevel(0);

            RenderToFogSurface.BeginScene(RenderSurface);

            MyDevice.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Black, 1.0f, 0);
            FogEffect.Technique = "dof";

            FogEffect.SetValue("xWorld", Matrix.Identity);
            FogEffect.SetValue("xView", mView);
            FogEffect.SetValue("xProjection", mProjection);
            FogEffect.CommitChanges();

            foreach (IObject s in ObjectList)
            {
                int passes = FogEffect.Begin(0);
                for (int i = 0; i < passes; i++)
                {
                    FogEffect.BeginPass(i);
                    s.Render(ref FogEffect, "xTexture", false);
                    FogEffect.EndPass();
                }
                FogEffect.End();
            }
            MyEnvironment.RenderTerrain(FogEffect, "dof");

            RenderToFogSurface.EndScene(Filter.None);
        }
        //////////////////////////////////////////////////////////////////////////
        private void RenderScene()
        {
            Surface RenderSurface = ScreenTexture.GetSurfaceLevel(0);

            RenderToScreenSurface.BeginScene(RenderSurface);
            MyDevice.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Black, 1.0f, 0);

            DefaultEffect.Technique = "simplest";
            DefaultEffect.SetValue("xWorld", Matrix.Identity);
            DefaultEffect.SetValue("xView", mView);
            DefaultEffect.SetValue("xProjection", mProjection);
            DefaultEffect.CommitChanges();

            RenderObjects(ref DefaultEffect);

            MyEnvironment.RenderTerrain(null, null);

            RenderToScreenSurface.EndScene(Filter.None);
        }
        //////////////////////////////////////////////////////////////////////////
        private void RenderObjects(ref Effect effect, bool with_helper_obj = true)
        {
            foreach (IObject s in ObjectList)
            {
                if (!with_helper_obj && s.GetTypeName() != "_MODEL_OBJECT")
                    continue;

                int passes = effect.Begin(0);
                for (int i = 0; i < passes; i++)
                {
                    effect.BeginPass(i);
                    s.Render(ref effect, "xTexture");
                    effect.EndPass();
                }
                effect.End();
            }
        }
    }
}
